package com.duan.laugh.event.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.Method;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.duan.laugh.common.event.constants.EventConstant;
import com.duan.laugh.common.event.pojo.dto.EventDTO;
import com.duan.laugh.common.security.util.UserUtils;
import com.duan.laugh.event.mapper.EventHandlerMapper;
import com.duan.laugh.event.pojo.entity.AfterEvent;
import com.duan.laugh.event.pojo.entity.EventHandler;
import com.duan.laugh.event.pojo.entity.EventNotice;
import com.duan.laugh.event.pojo.vo.EventHandlerRuleVO;
import com.duan.laugh.event.service.IEventHandlerRuleService;
import com.duan.laugh.event.service.IEventHandlerService;
import com.duan.laugh.msg.api.constans.MsgConstants;
import com.duan.laugh.msg.api.pojo.DTO.MsgDTO;
import com.duan.laugh.msg.api.pojo.entity.MsgSend;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.constraints.NotNull;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 事件处理 服务实现类
 * </p>
 *
 * @author liutf
 * @since 2019-11-12
 */
@Slf4j
@Service
public class EventHandlerServiceImpl extends ServiceImpl<EventHandlerMapper, EventHandler> implements IEventHandlerService {

    @Autowired
    private AmqpTemplate amqpTemplate;
    @Autowired
    private IEventHandlerRuleService eventHandlerRuleService;

    /**
     * 0010506 新增事件处理
     *
     * @param eventHandler
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insertEventHandler(EventHandler eventHandler) {
        // 新增事件处理
        super.save(eventHandler);
        // 获取消息参数
        String params = eventHandler.getParams();
        JSONObject paramJsonObject = null;
        if (StrUtil.isNotBlank(params)) {
            paramJsonObject = JSONObject.parseObject(params);
        }
        // 如果有事件处理规则，根据事件处理规则的handing生成消息或待办（0010211）
        List<EventHandlerRuleVO> eventHandlerRuleVOS = eventHandlerRuleService.selectEventHandlerRuleVOSByModelId(eventHandler.getEventModelId());
        MsgDTO msgDTO = null;
        List msgSends = null;
        MsgSend msgSend = null;
        EventNotice eventNotice = null;
        EventHandlerRuleVO eventHandlerRuleVO = null;
        for (int i = 0, n = eventHandlerRuleVOS.size(); i < n; i++) {
            eventHandlerRuleVO = eventHandlerRuleVOS.get(i);

            // 事件处理规则为自动处理，触发 url 事件
            if (EventConstant.HANDLING_TYPE_AUTO.equals(eventHandlerRuleVO.getHandling())) {
                this.actionUrl(eventHandlerRuleVO.getUrl(), params);
            }

            // 生成消息或待办
            msgDTO = new MsgDTO();
            msgDTO.setHandling(eventHandlerRuleVO.getHandling())
                    .setEventId(eventHandlerRuleVO.getEventModelId())
                    .setHandler(eventHandlerRuleVO.getHandler())
                    .setTenantId(eventHandler.getTenantId())
                    .setCreateTime(LocalDateTime.now());

            // 事件通知，放到mq队列
            msgSends = new ArrayList<MsgSend>();
            List<EventNotice> eventNotices = eventHandlerRuleVO.getEventNotices();
            for (int i1 = 0, n1 = eventNotices.size(); i1 < n1; i1++) {
                eventNotice = eventNotices.get(i1);
                Integer delay = eventNotice.getDelay();
                // 组装数据
                msgSend = new MsgSend();
                msgSend.setReceiveObject(eventNotice.getReceiveObject())
                        .setReceiveObjectValue(eventNotice.getReceiveObjectValue())
                        .setCreateTime(LocalDateTime.now()).setType(eventNotice.getType())
                        .setTemplateId(eventNotice.getTemplateId()).setSendTime(LocalDateTime.now().plusSeconds(delay))
                        .setParams(params).setTenantId(eventHandler.getTenantId());
                msgSends.add(msgSend);
            }
            msgDTO.setMsgSends(msgSends);

            amqpTemplate.convertAndSend(MsgConstants.MQ_QUEUE_MSG, msgDTO);
            log.info("已将" + eventNotices.size() + "条消息通知放入消息队列");

            // 如果有后续事件，并且满足后续事件的表达式（空表达式返回true）则生成事件处理（0010506）；
            List<AfterEvent> afterEvents = eventHandlerRuleVO.getAfterEvents();
            for (int i1 = 0, n1 = afterEvents.size(); i1 < n1; i1++) {
                AfterEvent afterEvent = afterEvents.get(i1);
                // 后续事件条件
                String afterEventCondition = afterEvent.getAfterEventCondition();

                log.info("检测到后续事件，条件 ... 运算成功");

                // 根据触发时间字段获取触发时间
                LocalDateTime detonateTime = this.getDetonateTime(paramJsonObject, eventHandlerRuleVO.getTiggerTimeField());
                // 发送时间：触发时间 + 延迟时间（秒）
                LocalDateTime plusSeconds = detonateTime.plusSeconds(afterEvent.getDelay());

                // 组装事件数据放入事件队列
                EventDTO eventDTO = new EventDTO();
                Map<String, String> map = Maps.newHashMap();
                map.put("eventModelId", String.valueOf(afterEvent.getEventModelId()));
                map.put("params", params);
                eventDTO.setType(EventConstant.EVENT_TYPE_ID);
                eventDTO.setEventData(map);
                // 放入后续事件延迟队列
//                amqpTemplate.convertAndSend(EventConstant.MQ_QUEUE_EVENT, eventDTO);
                this.sendMsgByDelay(eventDTO, plusSeconds, EventConstant.MQ_EXCHANGE_EVENT_DELAY, EventConstant.MQ_QUEUE_EVENT_DELAY);
                log.info("后续事件已放入延迟队列");
            }
        }
        log.info("事件处理完毕");
    }

    /**
     * 获取触发时间
     *
     * @param paramJsonObject jsonObject
     * @param tiggerTimeField 触发时间字段
     * @return 获取不到或者获取到历史时间，则设为当前时间
     */
    private LocalDateTime getDetonateTime(JSONObject paramJsonObject, String tiggerTimeField) {
        LocalDateTime detonateTime;
        if (paramJsonObject != null) {
            // 获取结果
            String value = String.valueOf(paramJsonObject.get(tiggerTimeField));
            // 获取不到设为当前时间
            if (StrUtil.isBlank(value)) {
                detonateTime = LocalDateTime.now();
            } else {
                // 能获取到
                detonateTime = LocalDateTime.parse(value);
                // 如果为历史时间
                if (detonateTime.compareTo(LocalDateTime.now()) < 0) {
                    log.error("触发时间为过去时间，改为立即触发......");
                    detonateTime = LocalDateTime.now();
                }
            }
        } else {
            detonateTime = LocalDateTime.now();
        }
        return detonateTime;
    }

    /**
     * 请求指定 url
     *
     * @param actionUrl method + " " + url
     * @param params    json格式参数
     */
    public void actionUrl(String actionUrl, String params) {
        if (StrUtil.isNotBlank(actionUrl)) {
            final String[] s = actionUrl.split(" ");
            final String method = s[0].toUpperCase();
            final String url = s[1];
            String response;
            final HttpRequest httpRequest = new HttpRequest(url).method(Method.valueOf(method));
            // 如果http是GET方法，参数放到url后；否则，参数放到body
            try {
                if (Method.GET.name().equals(method)) {
                    httpRequest.form(JSONObject.toJavaObject(JSONObject.parseObject(params), Map.class));
                } else {
                    httpRequest.body(JSONObject.toJSONString(params));
                }
                // 发送http请求
                response = httpRequest.execute().body();
                log.info("调用接口：{}，返回值：{}", httpRequest.getUrl(), response);
            } catch (HttpException e) {
                log.error("调用接口失败：接口：{}，参数：{}", actionUrl, params);
            }
        }
    }

    /**
     * 放入后续事件延迟队列
     *
     * @param msgSend
     * @param mqExchangeQueueDelay
     * @param mqQueueDelay
     */
    private void sendMsgByDelay(EventDTO eventDTO, LocalDateTime sendTime, String mqExchangeQueueDelay, String mqQueueDelay) {
        log.info("后续事件的发送时间：【{}】", sendTime);
        LocalDateTime now = LocalDateTime.now();
        long delayMillis = Duration.between(now, sendTime).toMillis();
        log.info("现在的时间：【{}】", now);
        log.info("延迟发送的时间差为(毫秒)：【{}】", delayMillis);
        amqpTemplate.convertAndSend(mqExchangeQueueDelay, mqQueueDelay, eventDTO, message -> {
            // 发送时间 - 现在时间 = 延迟时间（单位毫秒）
            message.getMessageProperties().setHeader("x-delay", delayMillis);
            return message;
        });
    }
}
